package drds.server.config.model;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;

import drds.server.api_warpper.ReadWriteLock;
import drds.server.util.SplitUtil;

/**
 * 在系统初始化的时候 -也就是配置的时候 我们需要将所有的参数都小写。除了全局表,其他表都需要带上分片键。
 * 
 * @author 曹正辉<330937205@qq.com>
 */
public class Table {

	/** 表名 */
	private final String name;
	/** 表类型 */
	public static final int TYPE_GLOBAL_TABLE = 1;// 全局表
	public static final int TYPE_NOT_SHARD = 2;// 不分库分表的表
	public static final int TYPE_NOT_GLOBAL_TABLE_AND_NOT_ER = 2;// 单表
	public static final int TYPE_NOT_GLOBAL_TABLE_AND_ER_PARENT_TABLE = 3;
	public static final int TYPE_NOT_GLOBAL_TABLE_AND_ER_CHILD_TABLE = 4;
	public static final int TYPE_NOT_GLOBAL_TABLE_AND_ER_LINK_TABLE = 4;
	private final int tableType;// 表类型
	//
	private ShardKeyType shardkeyType;// 切分键类型final
	/** 主键列名 */
	private final String primaryKeyName;
	// 是否采用自增(如果不是自增则采用业务设置主键的形式注入值)
	private final boolean useGlobalSequence;
	private boolean isAutoSetSequenceNo;

	private final boolean needAddLimit;

	private final ArrayList<String> dataNodeList;// 需要采用专门的表路由字典进行记录,此处记录其引用信息.

	/** 表规则配置 */
	private final ShardRule shardRule;
	private final String shardColumnName;
	//
	private ShardKey firstShardKey;
	private ShardKey secondShardKey;

	private final boolean isChildTable;
	private final Table parentTable;
	// ROOT TABLE 该表的作用是快速通过rootParentTable来定位子表的信息
	@SuppressWarnings("unused")
	/**
	 * <pre>
	 * company where id=
	 *                 (select company_id from customer where id=3); 
	 * the one which return data (id) is the datanode to store child table's records
	 * </pre>
	 */
	private Table rootParentTable;
	/** 当前这张表和父表的关联键列名 */
	private final String joinKeyColumnName;
	/** 当前这张表父表的被关联键列名 */
	private final String parentTableJoinedKeyColumnName;
	private String rootParentTableKeyColumnName;// rootKeyName需要进行切分

	public String getRootParentTableKeyColumnName() {
		return rootParentTableKeyColumnName;
	}

	/** only has one level of parent,父表不再有父表 */
	private final boolean isSecondLevelChildTable;
	private final boolean shardKeyIsPrimaryKey;

	private volatile Map<String, List<String>> dataNodeTableStructureSQLMap;
	private ReadWriteLock readWriteLock = new ReadWriteLock(false);

	public Table(String name, int tableType, String primaryKeyName, boolean useGlobalSequence, boolean needAddLimit, String dataNodeString, ShardRule shardRule, boolean isChildTable, Table parentTable, String joinKeyColumnName, String parentTableJoinedKeyColumnName, Table rootParentTable, String rootParentTableKeyColumnName, String subTablesString2) {
		if (name == null) {
			throw new NullPointerException("name");
		}
		if (dataNodeString == null) {
			throw new NullPointerException("dataNode");
		}
		this.name = name;
		this.tableType = tableType;
		this.primaryKeyName = primaryKeyName;
		this.useGlobalSequence = useGlobalSequence;
		this.needAddLimit = needAddLimit;

		String[] dataNodes = SplitUtil.split(dataNodeString, ',', '$', '-');
		if (dataNodes == null || dataNodes.length <= 0) {
			throw new IllegalArgumentException("invalid table dataNodes: " + dataNodeString);
		}
		dataNodeList = new ArrayList<String>(dataNodes.length);
		for (String dataNode : dataNodes) {
			dataNodeList.add(dataNode);
		}
		this.shardRule = shardRule;
		this.shardColumnName = (shardRule == null) ? null : shardRule.getColumnName();
		this.shardKeyIsPrimaryKey = (shardColumnName == null) ? primaryKeyName == null : shardColumnName.equals(primaryKeyName);

		//
		this.isChildTable = isChildTable;
		this.parentTable = parentTable;
		this.setRootParentTable(this.getRootParentTable());// 快速定位的方法
		this.joinKeyColumnName = joinKeyColumnName;
		this.parentTableJoinedKeyColumnName = parentTableJoinedKeyColumnName;
		if (parentTable != null) {
			this.isSecondLevelChildTable = (parentTable.parentTable == null);
			this.rootParentTable = rootParentTable;
			this.rootParentTableKeyColumnName = rootParentTableKeyColumnName;
		} else {
			this.isSecondLevelChildTable = false;
		}
	}

	public String getPrimaryKeyName() {
		return primaryKeyName;
	}

	public boolean isUseGlobalSequence() {
		return useGlobalSequence;
	}

	public boolean isNeedAddLimit() {
		return needAddLimit;
	}

	public boolean isSecondLevelChildTable() {
		return isSecondLevelChildTable;
	}

	public boolean isGlobalTable() {
		return this.tableType == Table.TYPE_GLOBAL_TABLE;
	}

	public boolean isNotShardTable() {
		return this.tableType == Table.TYPE_NOT_SHARD;
	}

	public String genJoinRootParentTableSql() {
		Table table = this;
		StringBuilder tableName = new StringBuilder();
		StringBuilder condition = new StringBuilder();
		Table preTable = null;
		int level = 0;
		String $0LevelCondition = null;
		while (table.parentTable != null) {
			tableName.append(table.parentTable.name).append(',');

			if (level == 0) {
				$0LevelCondition = " " + table.parentTable.getName() + '.' + table.parentTableJoinedKeyColumnName + "=";
			} else {
				String relation = table.parentTable.getName() + '.' + table.parentTableJoinedKeyColumnName + '='//
						+ table.name + '.' + table.joinKeyColumnName;
				condition.append(relation).append(" AND ");
			}
			level++;
			preTable = table;
			table = table.parentTable;
		}
		String sql = "select " + preTable.parentTable.name + '.' + preTable.parentTableJoinedKeyColumnName + " FROM " + //
				tableName.substring(0, tableName.length() - 1) + // 去掉最后一个,
				" WHERE " + ((level < 2) ? $0LevelCondition : condition.toString() + $0LevelCondition);
		return sql;

	}

	public String getShardColumnName() {
		return shardColumnName;
	}

	public int getTableType() {
		return tableType;
	}

	/**
	 * get root parent table
	 */
	public Table getRootParentTable() {
		if (parentTable == null) {
			return null;
		}
		/**
		 * 之前的那个表,while循环直到父表没有的时候。
		 */
		Table preTable = parentTable;
		Table parentTable = preTable.getParentTable();
		while (parentTable != null) {
			preTable = parentTable;
			parentTable = parentTable.getParentTable();
		}
		return preTable;
	}

	public Table getParentTable() {
		return parentTable;
	}

	public boolean isChildTable() {
		return isChildTable;
	}

	public String getJoinKeyColumnName() {
		return joinKeyColumnName;
	}

	public String getParentTableJoinedKeyColumnName() {
		return parentTableJoinedKeyColumnName;
	}

	public String getName() {
		return name;
	}

	public ArrayList<String> getDataNodeList() {
		return dataNodeList;
	}

	public String getRandomNodeIndex() {
		Random rand = new Random();
		int index = Math.abs(rand.nextInt(Integer.MAX_VALUE)) % dataNodeList.size();
		return dataNodeList.get(index);
	}

	public ShardRule getShardRule() {
		return shardRule;
	}

	public boolean primaryKeyIsPartionKey() {
		return shardKeyIsPrimaryKey;
	}

	public ReadWriteLock getReadWriteLock() {
		return readWriteLock;
	}

	public void setReadWriteLock(ReadWriteLock readWriteLock) {
		this.readWriteLock = readWriteLock;
	}

	public Map<String, List<String>> getDataNodeTableStructureSQLMap() {
		return dataNodeTableStructureSQLMap;
	}

	public void setDataNodeTableStructureSQLMap(Map<String, List<String>> dataNodeTableStructureSQLMap) {
		this.dataNodeTableStructureSQLMap = dataNodeTableStructureSQLMap;
	}

	public void setRootParentTable(Table rootParentTable) {
		this.rootParentTable = rootParentTable;
	}

	public ShardKeyType getShardkeyType() {
		return shardkeyType;
	}

	public boolean isAutoSetSequenceNo() {
		return isAutoSetSequenceNo;
	}

	public void setAutoSetSequenceNo(boolean isAutoSetSequenceNo) {
		this.isAutoSetSequenceNo = isAutoSetSequenceNo;
	}

	public ShardKey getFirstShardKey() {
		return firstShardKey;
	}

	public ShardKey getSecondShardKey() {
		return secondShardKey;
	}
}